package cn.com.mybaits.handler;


import cn.com.mybaits.annotation.Param;
import cn.com.mybaits.annotation.Select;
import cn.com.mybaits.type.ParamType;
import cn.com.mybaits.type.impl.IntgerParamType;
import cn.com.mybaits.type.impl.StringParamType;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

/**
 * 调用处理程序my
 *
 * @author LiuGuodong
 * @date 2023/09/28
 */
public class InvocationHandlerMy implements InvocationHandler {

    private static Map<Class, ParamType> PARAMETER_TYPE_POLICY = new HashMap<>();

    static {
        PARAMETER_TYPE_POLICY.put(String.class, new StringParamType());
        PARAMETER_TYPE_POLICY.put(Integer.class, new IntgerParamType());
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
        try (Connection connection = getConnection();) {
            //获取sql 与 参数映射
            Select annotation = method.getAnnotation(Select.class);
            HashMap<String, Object> paramMap = new HashMap<>();
            //sql 需要替换的参数
            List<String> sqlParam = new ArrayList<>();
            //生成参数映射
            buildParameterMapping(method, args, paramMap);
            //获取sql
            String sql = getSql(annotation.value(), paramMap, sqlParam);
            try (PreparedStatement statement = connection.prepareStatement(sql);) {
                //设置参数
                setParameters(sqlParam, paramMap, statement);

                ResultSet resultSet = statement.executeQuery();
                //获取结果类型
                return getResult(method, resultSet);
            }
        }
    }


    private Object getResult(Method method, ResultSet resultSet)
            throws SQLException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Class resultType = getResultType(method);
        List<Object> result = new ArrayList<>();

        //建立set方法与参数名称的映射
        Map<String, Method> methodMapByParam = methodAndParameterNames(resultType);

        //保存查询的参数名称
        ResultSetMetaData metaData = resultSet.getMetaData();
        while (resultSet.next()) {
            Object newInstance = resultType.newInstance();
            result.add(newInstance);
            //根据查询的参数个数去set
            for (int i = 0; i < metaData.getColumnCount(); i++) {
                int index = i + 1;
                String columnName = metaData.getColumnName(index);
                Method set = methodMapByParam.get(columnName.toLowerCase());
                //获取方法参数类型
                Parameter parameter = set.getParameters()[0];
                Object getValue = PARAMETER_TYPE_POLICY.get(parameter.getType()).getParameter(resultSet, columnName);
                set.invoke(newInstance, getValue);
            }
        }
        if (result.isEmpty()) {
            return null;
        }

        if (!(method.getGenericReturnType() instanceof ParameterizedType) && result.size() == 1) {
            return result.get(0);
        }

        return result;
    }

    private void setParameters(List<String> sqlParam, HashMap<String, Object> paramMap, PreparedStatement statement) throws SQLException {
        for (int i = 0; i < sqlParam.size(); i++) {
            Object paramValue = paramMap.get(sqlParam.get(i));
            PARAMETER_TYPE_POLICY.get(paramValue.getClass()).setParameter(statement, paramValue, i + 1);
        }
    }

    private Map<String, Method> methodAndParameterNames(Class resultType) {
        Map<String, Method> methodMapByParam = new HashMap<>();
        for (Method typeMethod : resultType.getMethods()) {
            String methodName = typeMethod.getName();
            if (!methodName.startsWith("set")) {
                continue;
            }
            String key = methodName.substring(3).toLowerCase();
            methodMapByParam.put(key, typeMethod);
        }
        return methodMapByParam;
    }

    private Class getResultType(Method method) {
        Class resultType = null;
        Type returnType = method.getGenericReturnType();
        if (returnType instanceof ParameterizedType) {
            Type[] arguments = ((ParameterizedType) returnType).getActualTypeArguments();
            resultType = ((Class) arguments[0]);
        } else if (returnType instanceof Class) {
            resultType = (Class) returnType;
        }
        return resultType;
    }

    /**
     * 生成参数映射
     *
     * @param method   方法
     * @param args     args
     * @param paramMap 参数映射
     */
    private void buildParameterMapping(Method method, Object[] args, HashMap<String, Object> paramMap) {
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            String key = parameter.getAnnotation(Param.class).value();
            Object value = args[i];
            paramMap.put(key, value);
            paramMap.put(parameter.getName(), value);
        }
    }

    private String getSql(String sql, HashMap<String, Object> paramMap, List<String> sqlParam) {
        TreeMap<Integer, String> sortMap = new TreeMap<>();
        for (Entry<String, Object> entry : paramMap.entrySet()) {
            String str = "#{" + entry.getKey() + "}";
            int i = sql.indexOf(str);
            if (i < 0) {
                continue;
            }
            sortMap.put(i, entry.getKey());
            sql = sql.replace(str, "?");
        }
        sqlParam.addAll(sortMap.values());
        return sql;
    }

    private static Connection getConnection() throws SQLException {
        String url = "jdbc:mysql://localhost:3306/bit32mall?serverTimezone=UTC";
        String user = "root";
        String password = "mysql";
        return DriverManager.getConnection(url, user, password);
    }
}
